﻿using System;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;

namespace Microsoft.Win32
{
	internal static partial class NativeMethods
	{
		public class VisualStyle : IDisposable
		{
			IntPtr hTheme;
			bool ownHandle = true;

			public VisualStyle(string classList)
			{
				hTheme = OpenThemeData(IntPtr.Zero, classList);
				if (hTheme == IntPtr.Zero)
					throw new Win32Exception();
			}

			public VisualStyle(string classList, OpenThemeDataExFlags flags)
			{
				hTheme = OpenThemeDataEx(IntPtr.Zero, classList, flags);
				if (hTheme == IntPtr.Zero)
					throw new Win32Exception();
			}

			public VisualStyle(IntPtr handle)
			{
				hTheme = handle;
				ownHandle = false;
			}

			public VisualStyle(System.Windows.Forms.IWin32Window window, string classList)
			{
				hTheme = OpenThemeData(window.Handle, classList);
				if (hTheme == IntPtr.Zero)
					throw new Win32Exception();
			}

			~VisualStyle()
			{
				Dispose();
			}

			public void Dispose()
			{
				if (hTheme != IntPtr.Zero && ownHandle)
					CloseThemeData(hTheme);
				hTheme = IntPtr.Zero;
			}

			public System.Drawing.Bitmap GetBitmap(Graphics graphics, int partId, int stateId, int propId)
			{
				IntPtr hBmp;
				try
				{
					using (var hdc = new SafeGDIHandle(graphics))
						GetThemeBitmap(hTheme, hdc, partId, stateId, propId, 0, out hBmp);
				}
				catch { return null; }
				return System.Drawing.Bitmap.FromHbitmap(hBmp);
			}

			public bool? GetBool(int partId, int stateId, int propId)
			{
				int i;
				int r = GetThemeBool(hTheme, partId, stateId, propId, out i);
				if (r != 0)
					return null;
				return i != 0;
			}

			public Color? GetColor(int partId, int stateId, int propId)
			{
				int cr;
				int r = GetThemeColor(hTheme, partId, stateId, propId, out cr);
				if (r != 0)
					return null;
				return ColorTranslator.FromWin32(cr);
			}

			public int? GetEnumValue(int partId, int stateId, int propId)
			{
				int i;
				int r = GetThemeEnumValue(hTheme, partId, stateId, propId, out i);
				if (r != 0)
					return null;
				return i;
			}

			public string GetFilename(int partId, int stateId, int propId)
			{
				const int sbLen = 1024;
				StringBuilder sb = new StringBuilder(sbLen);
				int r = GetThemeFilename(hTheme, partId, stateId, propId, ref sb, sbLen);
				if (r != 0)
					return null;
				return sb.ToString();
			}

			public Font GetFont(Graphics graphics, int partId, int stateId, int propId)
			{
				LOGFONT f;
				try
				{
					using (var hdc = new SafeGDIHandle(graphics))
						NativeMethods.GetThemeFont(hTheme, hdc, partId, stateId, propId, out f);
				}
				catch { return null; }
				return f.ToFont();
			}

			public int? GetInt(int partId, int stateId, int propId)
			{
				int i;
				int r = GetThemeInt(hTheme, partId, stateId, propId, out i);
				if (r != 0)
					return null;
				return i;
			}

			public int[] GetIntList(int partId, int stateId) => NativeMethods.GetThemeIntList(hTheme, partId, stateId);

			public System.Windows.Forms.Padding? GetMargins(Graphics graphics, int partId, int stateId, int propId)
			{
				RECT rc;
				try
				{
					using (var hdc = new SafeGDIHandle(graphics))
						GetThemeMargins(hTheme, hdc, partId, stateId, propId, IntPtr.Zero, out rc);
				}
				catch { return null; }
				return new System.Windows.Forms.Padding(rc.Left, rc.Top, rc.Right, rc.Bottom);
			}

			public int? GetMetric(Graphics graphics, int partId, int stateId, int propId)
			{
				int i;
				try
				{
					using (var hdc = new SafeGDIHandle(graphics))
						GetThemeMetric(hTheme, hdc, partId, stateId, propId, out i);
				}
				catch { return null; }
				return i;
			}

			public object GetObject(Graphics graphics, int partId, int stateId, int propId)
			{
				object o = null;
				if (IsDefined(partId, stateId))
				{
					try
					{
						if ((propId >= 600 && propId <= 608))
							o = GetString(partId, stateId, propId);
						else if ((propId >= 801 && propId <= 806) || propId == 2601)
							o = GetFont(graphics, partId, stateId, propId);
						else if (propId == 1001 || (propId >= 2201 && propId <= 2213) || propId == 5001)
							o = GetBool(partId, stateId, propId);
						else if ((propId >= 1201 && propId <= 1209) || propId == 1301 || (propId >= 1801 && propId <= 1810) || propId == 5006)
							o = GetInt(partId, stateId, propId);
						else if ((propId >= 2401 && propId <= 2424))
							o = GetMetric(graphics, partId, stateId, propId);
						else if ((propId >= 1401 && propId <= 1402) || propId == 3201 || propId == 8001)
							o = GetString(partId, stateId, propId);
						else if ((propId >= 1601 && propId <= 1631) || (propId >= 2001 && propId <= 2010) || (propId >= 3801 && propId <= 3823) || propId == 5003)
							o = GetColor(partId, stateId, propId);
						else if ((propId >= 3001 && propId <= 3008) || propId == 7001)
							o = GetFilename(partId, stateId, propId);
						else if (propId >= 3401 && propId <= 3409)
							o = GetPosition(partId, stateId, propId);
						else if (propId >= 3601 && propId <= 3603)
							o = GetMargins(graphics, partId, stateId, propId);
						else if (propId >= 4001 && propId <= 4015)
							o = GetEnumValue(partId, stateId, propId);
						else if (propId == 5002 || propId == 5004 || propId == 5005 || propId == 8002)
							o = GetRect(graphics, partId, stateId, propId);
						else if (propId == 6000)
							o = GetIntList(partId, stateId);
						//else if (propId == 8000)
						//	o = GetDiskstream(partId, stateId, propId);
					}
					catch (Exception ex)
					{
						o = ex;
					}
				}
				return o;
			}

			public Size? GetPartSize(Graphics graphics, int partId, int stateId, Rectangle destRect, int themeSize)
			{
				SIZE sz;
				try
				{
					using (var hdc = new SafeGDIHandle(graphics))
					{
						RECT rc = new RECT(destRect);
						GetThemePartSize(hTheme, hdc, partId, stateId, ref rc, themeSize, out sz);
					}
				}
				catch { return null; }
				return sz.ToSize();
			}

			public Point? GetPosition(int partId, int stateId, int propId)
			{
				Point i;
				int r = GetThemePosition(hTheme, partId, stateId, propId, out i);
				if (r != 0)
					return null;
				return i;
			}

			public Rectangle? GetRect(Graphics graphics, int partId, int stateId, int propId)
			{
				RECT rc;
				try
				{
					using (var hdc = new SafeGDIHandle(graphics))
						GetThemeRect(hTheme, partId, stateId, propId, out rc);
				}
				catch { return null; }
				return rc;
			}

			public string GetString(int partId, int stateId, int propId)
			{
				const int sbLen = 1024;
				StringBuilder sb = new StringBuilder(sbLen);
				int r = GetThemeString(hTheme, partId, stateId, propId, ref sb, sbLen);
				if (r != 0)
					return null;
				return sb.ToString();
			}

			public bool IsDefined(int partId, int stateId) => 0 != IsThemePartDefined(hTheme, partId, 0);
		}

		[Flags]
		public enum OpenThemeDataExFlags : int
		{
			None = 0,
			ForceImageStretch = 1,
			AllowNonClientDrawing = 2
		}

		[DllImport(UXTHEME, ExactSpelling = true, PreserveSig = false)]
		static extern void CloseThemeData(IntPtr hTheme);

		[DllImport(UXTHEME, ExactSpelling = true)]
		static extern Int32 GetThemeBitmap(IntPtr hTheme, IntPtr hdc, int iPartId, int iStateId, int iPropId, int dwFlags, out IntPtr phBitmap);

		[DllImport(UXTHEME, ExactSpelling = true)]
		static extern Int32 GetThemeBool(IntPtr hTheme, int iPartId, int iStateId, int iPropId, out Int32 pfVal);

		[DllImport(UXTHEME, ExactSpelling = true)]
		static extern Int32 GetThemeColor(IntPtr hTheme, int iPartId, int iStateId, int iPropId, out Int32 pColor);

		[DllImport(UXTHEME, ExactSpelling = true)]
		static extern Int32 GetThemeEnumValue(IntPtr hTheme, int iPartId, int iStateId, int iPropId, out Int32 piVal);

		[DllImport(UXTHEME, ExactSpelling = true, CharSet = CharSet.Unicode)]
		static extern Int32 GetThemeFilename(IntPtr hTheme, int iPartId, int iStateId, int iPropId, ref StringBuilder pszBuff, int buffLength);

		[DllImport(UXTHEME, ExactSpelling = true)]
		static extern Int32 GetThemeInt(IntPtr hTheme, int iPartId, int iStateId, int iPropId, out Int32 piVal);

		#region GetThemeIntList

		public static int[] GetThemeIntList(IntPtr hTheme, int iPartId, int iStateId, int iPropId = (int)IntegerListProperty.TransitionDuration)
		{
			if (Environment.OSVersion.Version.Major < 6)
			{
				INTLIST_OLD l;
				int r = GetThemeIntListPreVista(hTheme, iPartId, iStateId, iPropId, out l);
				if (r != 0)
					return null;
				int[] outlist = new int[l.iValueCount];
				for (int i = 0; i < l.iValueCount; i++)
					outlist[i] = l.iValues[i];
				return outlist;
			}
			else
			{
				INTLIST l;
				int r = GetThemeIntList(hTheme, iPartId, iStateId, iPropId, out l);
				if (r != 0)
					return null;
				int[] outlist = new int[l.iValueCount];
				for (int i = 0; i < l.iValueCount; i++)
					outlist[i] = l.iValues[i];
				return outlist;
			}
		}

		[DllImport(UXTHEME, ExactSpelling = true)]
		static extern Int32 GetThemeIntList(IntPtr hTheme, int iPartId, int iStateId, int iPropId, out INTLIST pIntList);

		[DllImport(UXTHEME, EntryPoint = "GetThemeIntList")]
		static extern Int32 GetThemeIntListPreVista(IntPtr hTheme, int iPartId, int iStateId, int iPropId, out INTLIST_OLD pIntList);

		[StructLayout(LayoutKind.Sequential)]
		struct INTLIST
		{
			public int iValueCount;
			[MarshalAs(UnmanagedType.ByValArray, SizeConst = 402)]
			public int[] iValues;
		}

		[StructLayout(LayoutKind.Sequential)]
		struct INTLIST_OLD
		{
			public int iValueCount;
			[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
			public int[] iValues;
		}

		#endregion

		[DllImport(UXTHEME, ExactSpelling = true)]
		static extern Int32 GetThemeMetric(IntPtr hTheme, IntPtr hdc, int iPartId, int iStateId, int iPropId, out Int32 piVal);

		[DllImport(UXTHEME, ExactSpelling = true)]
		static extern Int32 GetThemePartSize(IntPtr hTheme, IntPtr hdc, int part, int state, ref RECT pRect, int eSize, out SIZE size);

		[DllImport(UXTHEME, ExactSpelling = true)]
		static extern Int32 GetThemePosition(IntPtr hTheme, int iPartId, int iStateId, int iPropId, out System.Drawing.Point piVal);

		[DllImport(UXTHEME, ExactSpelling = true)]
		static extern Int32 GetThemeRect(IntPtr hTheme, /*IntPtr hdc,*/ int iPartId, int iStateId, int iPropId, out RECT pRect);

		[DllImport(UXTHEME, ExactSpelling = true, CharSet = CharSet.Unicode)]
		static extern Int32 GetThemeString(IntPtr hTheme, int iPartId, int iStateId, int iPropId, ref StringBuilder themeString, int themeStringLength);

		[DllImport(UXTHEME, ExactSpelling = true)]
		static extern Int32 IsThemePartDefined(IntPtr hTheme, int iPartId, int iStateId);

		[DllImport(UXTHEME, ExactSpelling = true, CharSet = CharSet.Unicode)]
		static extern IntPtr OpenThemeData(IntPtr hWnd, String classList);

		[DllImport(UXTHEME, ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
		static extern IntPtr OpenThemeDataEx(IntPtr hWnd, string pszClassIdList, OpenThemeDataExFlags dwFlags);
	}
}